package com.gengzp.common.aspect;

import com.gengzp.common.annotations.OperationLogIgnore;
import com.gengzp.common.context.TraceContextHolder;
import com.gengzp.common.model.log.req.CreateOperationLogReq;
import com.gengzp.common.utils.HttpRequestUtils;
import com.gengzp.common.utils.JsonUtils;
import com.gengzp.permission.utils.PermissionUtils;
import com.gengzp.rpc.utils.RpcUtils;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Objects;
import java.util.Optional;

/**
 * @ClassName TraceAspect
 * @Description 请求链路追踪切面
 * @Author gengzp
 * @Date 2025/9/28 13:24
 */
@Aspect
@Component
public class TraceAspect {

    private static final Logger logger = LoggerFactory.getLogger(TraceAspect.class);

    /**
     * 日志同步服务的服务名 (系统服务)
     */
    private static final String LOG_SERVICE_NAME = "system";

    /**
     * 链路追踪日志创建uri
     */
    private static final String createTraceLogUri = "/log/operation/create";

    /**
     * 切入 controller 控制层方法
     */
    @Around("execution(* com.gengzp.*.controller..*(..))")
    public Object doControllerAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 只有被 traceId 管理的请求才会打印日志
        if (StringUtils.isBlank(TraceContextHolder.getTraceId())) {
            return joinPoint.proceed();
        }

        Date startTimeDate = new Date();
        long startTime = System.currentTimeMillis();
        String startCallLog = String.format("接收到请求: %s, 开始调用 -> %s.%s()%s",
                TraceContextHolder.getCurrentControllerUrl(),
                joinPoint.getSignature().getDeclaringType().getName(),
                joinPoint.getSignature().getName(),
                Objects.isNull(PermissionUtils.getCurrentUserId()) ?
                        ", 访问者未登录" :
                        String.format(", 当前用户id: %s, 昵称: %s",
                                PermissionUtils.getCurrentUserId(), PermissionUtils.getSuperAdminNickname()));
        logger.info(startCallLog);
        Object result = joinPoint.proceed();
        long consumeTime = System.currentTimeMillis() - startTime;
        logger.info("请求执行完成, 结束调用 -> {}.{}(), 本次请求总共耗时: {}ms\n",
                joinPoint.getSignature().getDeclaringType().getName(),
                joinPoint.getSignature().getName(), consumeTime);

        // controller 接口执行完成后, 还需额外保存接口调用日志 (带有 @OperationLogIgnore 注解的方法则不保存)
        if (Objects.isNull(((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(OperationLogIgnore.class))) {
            new Thread(() -> HttpRequestUtils.post(
                    "http://" + RpcUtils.getAddressByServiceName(LOG_SERVICE_NAME) + createTraceLogUri,
                    buildCreateOperationLogReqByJoinPoint(joinPoint, startTimeDate, consumeTime))).start();
        }

        return result;
    }

    /**
     * 切入所有业务接口方法
     */
    @Around("execution(* com.gengzp.*.service..*(..)) || " +
            "execution(* com.gengzp.*.facade..*(..)) || " +
            "execution(* com.gengzp.*.mapper..*(..))")
    public Object doBusinessAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 只有被 traceId 管理的请求才会打印日志
        if (StringUtils.isBlank(TraceContextHolder.getTraceId())) {
            return joinPoint.proceed();
        }

        long startTime = System.currentTimeMillis();
        logger.info("开始调用 -> {}.{}()", joinPoint.getSignature().getDeclaringType().getName(),
                joinPoint.getSignature().getName());
        Object result = joinPoint.proceed();
        logger.info("结束调用 -> {}.{}(), 耗时: {}ms", joinPoint.getSignature().getDeclaringType().getName(),
                joinPoint.getSignature().getName(), System.currentTimeMillis() - startTime);
        return result;
    }

    /**
     * 组装创建请求操作日志参数
     *
     * @param joinPoint   切点
     * @param startTime   开始时间
     * @param consumeTime 耗时
     * @return 创建操作日志参数
     */
    private CreateOperationLogReq buildCreateOperationLogReqByJoinPoint(ProceedingJoinPoint joinPoint,
                                                                        Date startTime,
                                                                        Long consumeTime) {
        CreateOperationLogReq operationLogReq = new CreateOperationLogReq();
        operationLogReq.setTraceId(TraceContextHolder.getTraceId());
        operationLogReq.setUri(TraceContextHolder.getCurrentControllerUrl());
        operationLogReq.setParam(Optional.ofNullable(joinPoint.getArgs()).map(JsonUtils::toJsonString).orElse(null));
        operationLogReq.setStartTime(startTime);
        operationLogReq.setConsumeTime(consumeTime);
        operationLogReq.setOperatorId(
                Optional.ofNullable(PermissionUtils.getCurrentUserId()).map(Long::valueOf).orElse(null));

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        ApiOperation annotation = signature.getMethod().getAnnotation(ApiOperation.class);
        if (Objects.nonNull(annotation)) {
            operationLogReq.setRequestDescription(annotation.value());
        }

        return operationLogReq;
    }

}
